#include "client.hh"
#include "../../protocol_files/dispatcher/msg_dispatcher.hh"
#include "../../corelib/object_pool.hpp"

Client::Client(SessionID sessionID)
    : sessionID_(sessionID), dtor_(false) {
}

Client::~Client() {
    dtor_ = true;
    for (auto& it : pathMap_) {
        if (it.second.pathID != INVALID_PATH_ID) {
            FrameworkRef.closePath(it.second.pathID);
        }
        for (auto& packet : it.second.UnsentPackets) {            
            MempoolRef.dispose(packet.msg);
            packet.msg = nullptr;
        }
    }
    pathMap_.clear();
}

void Client::fromClient(std::uint32_t msgID, const char * msg, std::size_t length) {
    // TODO decrypt
    auto siteType = MsgDispatcherRef.getSiteType(msgID);
    if (siteType == INVALID_SITE_TYPE) {
        return;
    }
    PathInfo* pathInfo = nullptr;
    auto it = pathMap_.find(siteType);
    if (it == pathMap_.end()) {
        pathInfo = &pathMap_[siteType];
    } else {
        pathInfo = &it->second;
    }
    if (!pathInfo) {
        return;
    }
    if (pathInfo->pathID == INVALID_PATH_ID) {
        if (pathInfo->UnsentPackets.size() > FrameworkRef.getMaxBufferredMsgCount()) {
            // Out of range of MaxBufferredMsgCount, It's impossible for legal client
            return;
        }
        // no path for it just buffer it
        pathInfo->UnsentPackets.emplace_back(UnsentPacket(msg, length));
        if (!pathInfo->finding) {
            pathInfo->finding = true;
            // try to find the path
            FrameworkRef.findPath(siteType, sessionID_);
        }
        return;
    }
    // flush the unsent packet first
    flush(pathInfo);
    if (!FrameworkRef.talkPath(pathInfo->pathID, msg, length)) {
        pathInfo->UnsentPackets.emplace_back(UnsentPacket(msg, length));
    }
}

void Client::toClient(const char * msg, std::size_t length) {
    // TODO ecrypt
    if (sessionID_ == INVALID_SESSION_ID) {
        return;
    }
    FrameworkRef.talkSession(sessionID_, msg, length);
}

void Client::toClient(const Message & msg) {
    // TODO ecrypt
    FrameworkRef.talkSession(sessionID_, msg);
}

void Client::newPath(PathID pathID, SiteType siteType) {
    auto& pathInfo = pathMap_[siteType];
    pathInfo.pathID = pathID;
    pathInfo.finding = false;
    flush(&pathInfo);
}

void Client::removePath(PathID pathID, SiteType siteType) {
    if (dtor_) {
        return;
    }
    auto it = pathMap_.find(siteType);
    if (it == pathMap_.end()) {
        errorlog << "There's no path for 'siteType'" << siteType << endlog;
        return;
    }
    it->second.pathID = INVALID_PATH_ID;
    it->second.finding = true;
    // try to find another path
    FrameworkRef.findPath(siteType, sessionID_);
}

SessionID Client::getSessionID() {
    return sessionID_;
}

void Client::flush(PathInfo* pathInfo) {
    auto& packets = pathInfo->UnsentPackets;
    for (auto it = packets.begin(); it != packets.end();) {
        if (FrameworkRef.talkPath(pathInfo->pathID, it->msg, it->length)) {
            it = packets.erase(it);
        } else {
            return;
        }
    }
}

ClientManager::ClientManager() {
}

ClientManager::~ClientManager() {
    for (auto client : clientMap_) {
        dispose(client);
    }
}

bool ClientManager::add(SessionID sessionID) {
    auto client = allocate<Client>(sessionID);
    if (!client) {
        return false;
    }
    clientMap_[sessionID] = client;
    return true;
}

bool ClientManager::remove(SessionID sessionID) {
    auto it = clientMap_.find(sessionID);
    if (it == clientMap_.end()) {
        return false;
    }
    dispose(it->second);
    clientMap_.erase(it);
    return true;
}

Client * ClientManager::get(SessionID sessionID) {
    auto it = clientMap_.find(sessionID);
    if (it == clientMap_.end()) {
        return nullptr;
    }
    return it->second;
}

void ClientManager::broadcast(const Message & msg) {
    for (auto& it : clientMap_) {
        it.second->toClient(msg);
    }
}

std::size_t ClientManager::size() {
    return clientMap_.size();
}
